#ifndef OHOS_RIVE_BINDINGS_COMMAND_QUEUE_H
#define OHOS_RIVE_BINDINGS_COMMAND_QUEUE_H
#include "helpers/exception_handler.h"
#include "helpers/factories.h"
#include "helpers/resource.h"
#include "rive/animation/state_machine_instance.hpp"
#include "rive/command_queue.hpp"
#include "rive/command_server.hpp"
#include "rive/file.hpp"
#include <string>

namespace ohos_rive {
napi_value CommandQueueConstructor(napi_env env, napi_callback_info info);
napi_value CommandQueueDelete(napi_env env, napi_callback_info info);
napi_value CommandQueueCreateListeners(napi_env env, napi_callback_info info);
napi_value CommandQueueCreateRenderTarget(napi_env env, napi_callback_info info);
napi_value CommandQueueLoadFile(napi_env env, napi_callback_info info);
napi_value CommandQueueDeleteFile(napi_env env, napi_callback_info info);
napi_value CommandQueueGetArtboardNames(napi_env env, napi_callback_info info);
napi_value CommandQueueGetStateMachineNames(napi_env env, napi_callback_info info);
napi_value CommandQueueGetViewModelNames(napi_env env, napi_callback_info info);
napi_value CommandQueueGetViewModelInstanceNames(napi_env env, napi_callback_info info);
napi_value CommandQueueGetViewModelProperties(napi_env env, napi_callback_info info);
napi_value CommandQueueGetEnums(napi_env env, napi_callback_info info);
napi_value CommandQueueCreateDefaultArtboard(napi_env env, napi_callback_info info);
napi_value CommandQueueCreateArtboardByName(napi_env env, napi_callback_info info);
napi_value CommandQueueDeleteArtboard(napi_env env, napi_callback_info info);
napi_value CommandQueueCreateDefaultStateMachine(napi_env env, napi_callback_info info);
napi_value CommandQueueCreateStateMachineByName(napi_env env, napi_callback_info info);
napi_value CommandQueueDeleteStateMachine(napi_env env, napi_callback_info info);
napi_value CommandQueueAdvanceStateMachine(napi_env env, napi_callback_info info);
napi_value CommandQueueNamedVMCreateBlankVMI(napi_env env, napi_callback_info info);
napi_value CommandQueueDefaultVMCreateBlankVMI(napi_env env, napi_callback_info info);
napi_value CommandQueueNamedVMCreateDefaultVMI(napi_env env, napi_callback_info info);
napi_value CommandQueueDefaultVMCreateDefaultVMI(napi_env env, napi_callback_info info);
napi_value CommandQueueNamedVMCreateNamedVMI(napi_env env, napi_callback_info info);
napi_value CommandQueueDefaultVMCreateNamedVMI(napi_env env, napi_callback_info info);
napi_value CommandQueueReferenceNestedVMI(napi_env env, napi_callback_info info);
napi_value CommandQueueDeleteViewModelInstance(napi_env env, napi_callback_info info);
napi_value CommandQueueBindViewModelInstance(napi_env env, napi_callback_info info);
napi_value CommandQueueSetNumberProperty(napi_env env, napi_callback_info info);
napi_value CommandQueueGetNumberProperty(napi_env env, napi_callback_info info);
napi_value CommandQueueSetStringProperty(napi_env env, napi_callback_info info);
napi_value CommandQueueGetStringProperty(napi_env env, napi_callback_info info);
napi_value CommandQueueSetBooleanProperty(napi_env env, napi_callback_info info);
napi_value CommandQueueGetBooleanProperty(napi_env env, napi_callback_info info);
napi_value CommandQueueSetEnumProperty(napi_env env, napi_callback_info info);
napi_value CommandQueueGetEnumProperty(napi_env env, napi_callback_info info);
napi_value CommandQueueSetColorProperty(napi_env env, napi_callback_info info);
napi_value CommandQueueGetColorProperty(napi_env env, napi_callback_info info);
napi_value CommandQueueFireTriggerProperty(napi_env env, napi_callback_info info);
napi_value CommandQueueSubscribeToProperty(napi_env env, napi_callback_info info);
napi_value CommandQueueUnsubscribeFromProperty(napi_env env, napi_callback_info info);
napi_value CommandQueueDecodeImage(napi_env env, napi_callback_info info);
napi_value CommandQueueDeleteImage(napi_env env, napi_callback_info info);
napi_value CommandQueueRegisterImage(napi_env env, napi_callback_info info);
napi_value CommandQueueUnregisterImage(napi_env env, napi_callback_info info);
napi_value CommandQueueDecodeAudio(napi_env env, napi_callback_info info);
napi_value CommandQueueDeleteAudio(napi_env env, napi_callback_info info);
napi_value CommandQueueRegisterAudio(napi_env env, napi_callback_info info);
napi_value CommandQueueUnregisterAudio(napi_env env, napi_callback_info info);
napi_value CommandQueueDecodeFont(napi_env env, napi_callback_info info);
napi_value CommandQueueDeleteFont(napi_env env, napi_callback_info info);
napi_value CommandQueueRegisterFont(napi_env env, napi_callback_info info);
napi_value CommandQueueUnregisterFont(napi_env env, napi_callback_info info);
napi_value CommandQueuePointerMove(napi_env env, napi_callback_info info);
napi_value CommandQueuePointerDown(napi_env env, napi_callback_info info);
napi_value CommandQueuePointerUp(napi_env env, napi_callback_info info);
napi_value CommandQueuePointerExit(napi_env env, napi_callback_info info);
napi_value CommandQueueCreateDrawKey(napi_env env, napi_callback_info info);
napi_value CommandQueuePollMessages(napi_env env, napi_callback_info info);
napi_value CommandQueueDraw(napi_env env, napi_callback_info info);
napi_value CommandQueueDeleteRiveSurface(napi_env env, napi_callback_info info);
napi_value CommandQueueDeleteListeners(napi_env env, napi_callback_info info);
napi_value CommandQueueCreateEglContext(napi_env env, napi_callback_info info);
napi_value CommandQueueDeleteEglContext(napi_env env, napi_callback_info info);
napi_value CommandQueueGetEglDisplay(napi_env env, napi_callback_info info);
napi_value CommandQueueCreateEglSurface(napi_env env, napi_callback_info info);

/** Convert a JVM long handle to a typed C++ handle */
template <typename HandleT>
static HandleT handleFromLong(int64_t handle)
{
    return reinterpret_cast<HandleT>(static_cast<uint64_t>(handle));
}

/** Convert a typed C++ handle to a JVM long handle */
template <typename HandleT>
static int64_t longFromHandle(HandleT handle)
{
    return static_cast<int64_t>(reinterpret_cast<uint64_t>(handle));
}

/**
 * Holds a reference to a Kotlin CommandQueue instance and allows calling
 * methods on it. Used by the Listener classes for callbacks.
 */
class NAPICommandQueue {
public:
    NAPICommandQueue(napi_env env, napi_value queue) : g_env(env)
    {
        napi_create_reference(env, queue, 1, &g_queueRef);
    }

    ~NAPICommandQueue()
    {
        if (g_queueRef != nullptr) {
            napi_delete_reference(g_env, g_queueRef);
        }
    }

    [[nodiscard]] napi_env env() const
    {
        return g_env;
    }

    /**
     * Call a CommandQueue JS instance method.
     *
     * @param methodName JS method name
     * @param argc Number of arguments
     * @param argv Arguments array
     */
    template <typename... Args>
    void call(const char *methodName, size_t argc, napi_value *argv) const
    {
        napi_value queue;
        napi_status status = napi_get_reference_value(g_env, g_queueRef, &queue);
        if (status != napi_ok) {
            LOGE("Failed to get queue from reference");
            return;
        }
        //        napi_value method;
        //        status = napi_get_named_property(g_env, queue, methodName, &method);
        //        if (status != napi_ok) {
        //            LOGE("Failed to get method %s from queue", methodName);
        //            return;
        //        }
        //
        //        napi_value result;
        //        status = napi_call_function(g_env, queue, method, argc, argv, &result);
        //        if (status != napi_ok) {
        //            LOGE("Failed to call method %s", methodName);
        //        }
        NapiExceptionHandler::CallVoidMethod(g_env, queue, methodName, argc, argv);
    }

private:
    napi_env const g_env;
    napi_ref g_queueRef = nullptr;
};

class FileListener : public rive::CommandQueue::FileListener {
public:
    FileListener(napi_env env, napi_value queue) : rive::CommandQueue::FileListener(), g_queue(env, queue) {}

    virtual ~FileListener() = default;

    void onFileLoaded(const rive::FileHandle handle, uint64_t requestID) override
    {
        napi_value argv[2];
        napi_create_int64(g_queue.env(), requestID, &argv[0]);
        napi_create_int64(g_queue.env(), longFromHandle(handle), &argv[1]);
        g_queue.call("onFileLoaded", 2, argv);
    }

    void onFileError(const rive::FileHandle, uint64_t requestID, std::string error) override
    {
        napi_value argv[2];
        napi_create_int64(g_queue.env(), requestID, &argv[0]);

        napi_value jError;
        napi_create_string_utf8(g_queue.env(), error.c_str(), error.length(), &jError);
        argv[1] = jError;

        g_queue.call("onFileError", 2, argv);
    }

    void onArtboardsListed(const rive::FileHandle, uint64_t requestID, std::vector<std::string> artboardNames) override
    {
        napi_value argv[2];
        napi_create_int64(g_queue.env(), requestID, &argv[0]);

        napi_value array;
        napi_create_array(g_queue.env(), &array);
        for (size_t i = 0; i < artboardNames.size(); i++) {
            napi_value jsName;
            napi_create_string_utf8(g_queue.env(), artboardNames[i].c_str(), artboardNames[i].length(), &jsName);
            napi_set_element(g_queue.env(), array, i, jsName);
        }
        argv[1] = array;

        g_queue.call("onArtboardsListed", 2, argv);
    }

    void onViewModelsListed(const rive::FileHandle,
                            uint64_t requestID,
                            std::vector<std::string> viewModelNames) override
    {
        napi_value argv[2];
        napi_create_int64(g_queue.env(), requestID, &argv[0]);

        napi_value array;
        napi_create_array(g_queue.env(), &array);
        for (size_t i = 0; i < viewModelNames.size(); i++) {
            napi_value jsName;
            napi_create_string_utf8(g_queue.env(), viewModelNames[i].c_str(), viewModelNames[i].length(), &jsName);
            napi_set_element(g_queue.env(), array, i, jsName);
        }
        argv[1] = array;

        g_queue.call("onViewModelsListed", 2, argv);
    }

    void onViewModelInstanceNamesListed(const rive::FileHandle,
                                        uint64_t requestID,
                                        std::string,
                                        std::vector<std::string> instanceNames) override
    {
        napi_value argv[2];
        napi_create_int64(g_queue.env(), requestID, &argv[0]);

        napi_value array;
        napi_create_array(g_queue.env(), &array);
        for (size_t i = 0; i < instanceNames.size(); i++) {
            napi_value jsName;
            napi_create_string_utf8(g_queue.env(), instanceNames[i].c_str(), instanceNames[i].length(), &jsName);
            napi_set_element(g_queue.env(), array, i, jsName);
        }
        argv[1] = array;

        g_queue.call("onViewModelInstancesListed", 2, argv);
    }
    // TODO onViewModelPropertiesListed
    void onViewModelPropertiesListed(
        const rive::FileHandle,
        uint64_t requestID,
        std::string viewModelName,
        std::vector<rive::CommandQueue::FileListener::ViewModelPropertyData> properties) override
    {
        napi_env env = g_queue.env();
        napi_value argv[2];
        napi_create_int64(env, requestID, &argv[0]);

        napi_value array;
        napi_create_array(env, &array);

        for (size_t i = 0; i < properties.size(); i++) {
            const auto &property = properties[i];

            napi_value jProperty;
            napi_create_object(env, &jProperty);

            // Add property name
            napi_value jsName;
            napi_create_string_utf8(env, property.name.c_str(), property.name.length(), &jsName);
            napi_set_named_property(env, jProperty, "name", jsName);

            // Add property type
            napi_value jsType;
            napi_create_int32(env, static_cast<int32_t>(property.type), &jsType);
            napi_set_named_property(env, jProperty, "type", jsType);

            napi_set_element(env, array, i, jProperty);
        }
        argv[1] = array;

        g_queue.call("onViewModelPropertiesListed", 2, argv);
    }
    // TODO onViewModelEnumsListed
    void onViewModelEnumsListed(const rive::FileHandle,
                                uint64_t requestID,
                                std::vector<rive::ViewModelEnum> enums) override
    {
        napi_env env = g_queue.env();
        napi_value argv[2];
        napi_create_int64(env, requestID, &argv[0]);

        napi_value array;
        napi_create_array(env, &array);

        for (size_t i = 0; i < enums.size(); i++) {
            const auto &enumItem = enums[i];

            napi_value jEnum;
            napi_create_object(env, &jEnum);

            // Add enum name
            napi_value jsName;
            napi_create_string_utf8(env, enumItem.name.c_str(), enumItem.name.length(), &jsName);
            napi_set_named_property(env, jEnum, "name", jsName);

            // Add enum values
            napi_value jValues;
            napi_create_array(env, &jValues);
            for (size_t j = 0; j < enumItem.enumerants.size(); j++) {
                napi_value jValue;
                napi_create_string_utf8(env, enumItem.enumerants[j].c_str(), enumItem.enumerants[j].length(), &jValue);
                napi_set_element(env, jValues, j, jValue);
            }
            napi_set_named_property(env, jEnum, "values", jValues);

            napi_set_element(env, array, i, jEnum);
        }
        argv[1] = array;

        g_queue.call("onEnumsListed", 2, argv);
    }

private:
    NAPICommandQueue g_queue;
};

class ArtboardListener : public rive::CommandQueue::ArtboardListener {
public:
    ArtboardListener(napi_env env, napi_value queue) : rive::CommandQueue::ArtboardListener(), g_queue(env, queue) {}

    virtual ~ArtboardListener() = default;

    void onStateMachinesListed(const rive::ArtboardHandle,
                               uint64_t requestID,
                               std::vector<std::string> stateMachineNames) override
    {
        napi_env env = g_queue.env();
        napi_value argv[2];
        napi_create_int64(env, requestID, &argv[0]);

        napi_value array;
        napi_create_array(env, &array);
        for (size_t i = 0; i < stateMachineNames.size(); i++) {
            napi_value jsName;
            napi_create_string_utf8(env, stateMachineNames[i].c_str(), stateMachineNames[i].length(), &jsName);
            napi_set_element(env, array, i, jsName);
        }
        argv[1] = array;

        g_queue.call("onStateMachinesListed", 2, argv);
    }

private:
    NAPICommandQueue g_queue;
};

class StateMachineListener : public rive::CommandQueue::StateMachineListener {
public:
    StateMachineListener(napi_env env, napi_value queue)
        : rive::CommandQueue::StateMachineListener(), g_queue(env, queue)
    {
    }

    virtual ~StateMachineListener() = default;

    void onStateMachineSettled(const rive::StateMachineHandle smHandle, uint64_t requestID) override
    {
        napi_value argv[1];
        napi_create_int64(g_queue.env(), longFromHandle(smHandle), &argv[0]);
        g_queue.call("onStateMachineSettled", 1, argv);
    }

private:
    NAPICommandQueue g_queue;
};

class ViewModelInstanceListener : public rive::CommandQueue::ViewModelInstanceListener {
public:
    ViewModelInstanceListener(napi_env env, napi_value queue)
        : rive::CommandQueue::ViewModelInstanceListener(), g_queue(env, queue)
    {
    }

    virtual ~ViewModelInstanceListener() = default;

    void onViewModelDataReceived(const rive::ViewModelInstanceHandle vmiHandle,
                                 uint64_t requestID,
                                 rive::CommandQueue::ViewModelInstanceData data) override
    {
        napi_env env = g_queue.env();
        napi_value argv[4];
        napi_create_int64(env, requestID, &argv[0]);
        napi_create_int64(env, longFromHandle(vmiHandle), &argv[1]);

        napi_value propertyName;
        napi_create_string_utf8(env, data.metaData.name.c_str(), data.metaData.name.length(), &propertyName);
        argv[2] = propertyName;

        switch (data.metaData.type) {
            case rive::DataType::number:
                napi_create_double(env, data.numberValue, &argv[3]);
                g_queue.call("onNumberPropertyUpdated", 4, argv);
                break;
            case rive::DataType::string:
                napi_value strValue;
                napi_create_string_utf8(env, data.stringValue.c_str(), data.stringValue.length(), &strValue);
                argv[3] = strValue;
                g_queue.call("onStringPropertyUpdated", 4, argv);
                break;
            case rive::DataType::boolean:
                napi_value boolValue;
                napi_get_boolean(env, data.boolValue, &boolValue);
                argv[3] = boolValue;
                g_queue.call("onBooleanPropertyUpdated", 4, argv);
                break;
            case rive::DataType::enumType:
                napi_value enumValue;
                napi_create_string_utf8(env, data.stringValue.c_str(), data.stringValue.length(), &enumValue);
                argv[3] = enumValue;
                g_queue.call("onEnumPropertyUpdated", 4, argv);
                break;
            case rive::DataType::color:
                napi_create_int32(env, data.colorValue, &argv[3]);
                g_queue.call("onColorPropertyUpdated", 4, argv);
                break;
            case rive::DataType::trigger:
                napi_value undefined;
                napi_get_undefined(env, &undefined);
                argv[3] = undefined;
                g_queue.call("onTriggerPropertyUpdated", 4, argv);
                break;
            default:
                LOGE("Unknown ViewModelInstance property type: %d", static_cast<int>(data.metaData.type));
        }
    }

private:
    NAPICommandQueue g_queue;
};

class ImageListener : public rive::CommandQueue::RenderImageListener {
public:
    ImageListener(napi_env env, napi_value queue) : rive::CommandQueue::RenderImageListener(), g_queue(env, queue) {}

    virtual ~ImageListener() = default;

    void onRenderImageDecoded(const rive::RenderImageHandle handle, uint64_t requestID) override
    {
        napi_value argv[2];
        napi_create_int64(g_queue.env(), requestID, &argv[0]);
        napi_create_int64(g_queue.env(), longFromHandle(handle), &argv[1]);
        g_queue.call("onImageDecoded", 2, argv);
    }

    void onRenderImageError(const rive::RenderImageHandle, uint64_t requestID, std::string error) override
    {
        napi_value argv[2];
        napi_create_int64(g_queue.env(), requestID, &argv[0]);

        napi_value jError;
        napi_create_string_utf8(g_queue.env(), error.c_str(), error.length(), &jError);
        argv[1] = jError;

        g_queue.call("onImageError", 2, argv);
    }

private:
    NAPICommandQueue g_queue;
};

class AudioListener : public rive::CommandQueue::AudioSourceListener {
public:
    AudioListener(napi_env env, napi_value queue) : rive::CommandQueue::AudioSourceListener(), g_queue(env, queue) {}

    virtual ~AudioListener() = default;

    void onAudioSourceDecoded(const rive::AudioSourceHandle handle, uint64_t requestID) override
    {
        napi_value argv[2];
        napi_create_int64(g_queue.env(), requestID, &argv[0]);
        napi_create_int64(g_queue.env(), longFromHandle(handle), &argv[1]);
        g_queue.call("onAudioDecoded", 2, argv);
    }

    void onAudioSourceError(const rive::AudioSourceHandle, uint64_t requestID, std::string error) override
    {
        napi_value argv[2];
        napi_create_int64(g_queue.env(), requestID, &argv[0]);

        napi_value jError;
        napi_create_string_utf8(g_queue.env(), error.c_str(), error.length(), &jError);
        argv[1] = jError;

        g_queue.call("onAudioError", 2, argv);
    }

private:
    NAPICommandQueue g_queue;
};

class FontListener : public rive::CommandQueue::FontListener {
public:
    FontListener(napi_env env, napi_value queue) : rive::CommandQueue::FontListener(), g_queue(env, queue) {}

    virtual ~FontListener() = default;

    void onFontDecoded(const rive::FontHandle handle, uint64_t requestID) override
    {
        napi_value argv[2];
        napi_create_int64(g_queue.env(), requestID, &argv[0]);
        napi_create_int64(g_queue.env(), longFromHandle(handle), &argv[1]);
        g_queue.call("onFontDecoded", 2, argv);
    }

    void onFontError(const rive::FontHandle, uint64_t requestID, std::string error) override
    {
        napi_value argv[2];
        napi_create_int64(g_queue.env(), requestID, &argv[0]);

        napi_value jError;
        napi_create_string_utf8(g_queue.env(), error.c_str(), error.length(), &jError);
        argv[1] = jError;

        g_queue.call("onFontError", 2, argv);
    }

private:
    NAPICommandQueue g_queue;
};

} // namespace ohos_rive

#endif // OHOS_RIVE_BINDINGS_COMMAND_QUEUE_H
